﻿#region Header

// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Threading;
using ScrumTable.Common.Logging;

#endregion

namespace ScrumTable.Common.Threading
{
    /// <summary>
    /// Provides a message queue which can be used to queue worker items.
    /// The items will be executed in different thread context as worker
    /// threads. This behaviour corresponds to a ThreadPool instance.
    /// </summary>
    /// <remarks>
    /// You can instantiate multiple ThreadedMessageQueue instances which
    /// have different internal thread lists. So the ThreadedMessageQueue
    /// has another conduct than the global static Windows ThreadPool.
    /// </remarks>
    /// <typeparam name="TData">Type of the data argument objects to store.</typeparam>
    public class ThreadedMessageQueue<TData> : MessageQueue<TData>
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly Dictionary<int, WorkerThread> _workersInUse = new Dictionary<int, WorkerThread>();
        private readonly WorkerThread[] _workers = null;

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new ThreadPool instance.
        /// </summary>
        /// <param name="threadCount">Specifies the number of worker threads in the current thread pool.</param>
        public ThreadedMessageQueue(uint threadCount)
        {
            _workers = new WorkerThread[threadCount];

            for (int i = 0; i < threadCount; ++i)
            {
                _workers[i] = new WorkerThread(i, OnThreadError, OnThreadWorkFinished);
            }
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Stops the message queue and shuts it down.
        /// </summary>
        public override void Abort()
        {
            base.Abort();

            lock (_workers)
            {
                _workersInUse.Clear();

                for (int i = 0; i < _workers.Length; ++i)
                {
                    _workers[i].Stop();
                    _workers[i] = null;
                }
                Monitor.Pulse(_workers);
            }
        }

        // -------------------------------
        // Queue processing procedures
        // -------------------------------

        /// <summary>
        /// Executes the queued item. Override default queue behaviour in order to
        /// execute the items in separate worker threads.
        /// </summary>
        /// <param name="itemToProcess">Internal QueueItem to process.</param>
        protected override void ExecuteItem(QueueItem itemToProcess)
        {
            WorkerThread worker = null;

            lock (_workers)
            {
                while (!DoAbort && _workersInUse.Count == _workers.Length)
                {
                    Monitor.Wait(_workers);
                }

                if (!DoAbort)
                {
                    for (int i = 0; i < _workers.Length; ++i)
                    {
                        if (!_workersInUse.ContainsKey(_workers[i].Id))
                        {
                            worker = _workers[i];
                            _workersInUse.Add(worker.Id, worker);
                            break;
                        }
                    }
                }
            }

            if (worker != null)
            {
                worker.Process(itemToProcess);
            }
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        private void OnThreadError(object sender, ThreadingErrorEventArgs eventArgs)
        {
            OnError(eventArgs);
        }

        private void OnThreadWorkFinished(WorkerThread sender, EventArgs args)
        {
            lock (_workers)
            {
                _workersInUse.Remove(sender.Id);
                Monitor.Pulse(_workers);
            }
        }

        #endregion


        // -------------------------------
        // Nested Inner Classes
        // -------------------------------

        /// <summary>
        /// This class encapsulates an internal worker thread.
        /// </summary>
        private class WorkerThread
        {
            #region Declarations
            //--------------------------------------------------------------------
            // Declarations
            //--------------------------------------------------------------------

            private QueueItem _itemToProcess = null;
            private bool _doAbort = false;
            private readonly int _id = -1;
            private readonly ThreadWorkFinishHandler _finishHandler = null;
            private readonly ThreadingEventHandler _errorHandler = null;
            private readonly Thread _thread = null;

            #endregion

            #region Properties
            //--------------------------------------------------------------------
            // Properties
            //--------------------------------------------------------------------

            /// <summary>
            /// Specifies the event which is thrown if the work has been processed.
            /// </summary>
            /// <param name="sender">The worker thread which has thrown the event.</param>
            /// <param name="args">Event arguments.</param>
            internal delegate void ThreadWorkFinishHandler(WorkerThread sender, EventArgs args);

            /// <summary>
            /// Gets the unqiue id of the worker thread.
            /// </summary>
            internal int Id { get { return _id; } }

            #endregion

            #region Constructors / Destructor
            //--------------------------------------------------------------------
            // Constructors / Destructor
            //--------------------------------------------------------------------

            /// <summary>
            /// Creates a new WorkerThread instance.
            /// </summary>
            /// <param name="id">Unqiue id of the workerthread.</param>
            /// <param name="errorHandler">An event handler which is used if an error occurred.</param>
            /// <param name="finishHandler">An event handler which is used if a process is finished.</param>
            internal WorkerThread(int id, ThreadingEventHandler errorHandler, ThreadWorkFinishHandler finishHandler)
            {
                PreCondition.AssertNotNull(errorHandler, "errorHandler");
                PreCondition.AssertNotNull(finishHandler, "finishHandler");

                _id = id;
                _finishHandler = finishHandler;
                _errorHandler = errorHandler;

                _thread = new Thread(ProcessAsync);
                _thread.Name = string.Format("{0} - {1} @{2}", GetType().Name, id, _thread.ManagedThreadId);
                _thread.IsBackground = true;
                _thread.Start();
            }

            #endregion

            #region Methods
            //--------------------------------------------------------------------
            // Methods
            //--------------------------------------------------------------------

            /// <summary>
            /// Stops the current worker thread and shuts it down.
            /// </summary>
            internal void Stop()
            {
                lock (this)
                {
                    _doAbort = true;
                    Monitor.Pulse(this);
                }
            }

            /// <summary>
            /// Processes the specified QueueItem in the current worker thread.
            /// </summary>
            /// <param name="itemToProcess">Queue item which should be processed.</param>
            internal void Process(QueueItem itemToProcess)
            {
                PreCondition.AssertNotNull(itemToProcess, "itemToProcess");

                lock (this)
                {
                    _itemToProcess = itemToProcess;
                    Monitor.Pulse(this);
                }
            }

            private void ProcessAsync()
            {
                while (!_doAbort)
                {
                    ProcessItemAsync();
                    ProcessFinish();
                }
            }

            private void ProcessFinish()
            {
                _finishHandler(this, EventArgs.Empty);
            }

            private void ProcessItemAsync()
            {
                lock (this)
                {
                    while (!_doAbort && _itemToProcess == null)
                    {
                        Monitor.Wait(this);
                    }

                    if (!_doAbort)
                    {
                        ExecuteItemAsync();
                    }
                }
            }

            private void ExecuteItemAsync()
            {
                try
                {
                    QueueItem toProcess = _itemToProcess;
                    _itemToProcess = null;
                    toProcess.Execute();
                }
                catch (Exception error)
                {
                    try
                    {
                        Logger<ThreadedMessageQueue<TData>>.Error(error);

                        _errorHandler(
                            this,
                            new ThreadingErrorEventArgs(
                                new ThreadingException("Cannot execute the specified item.", error)));
                    }
                    catch (Exception logError)
                    {
                        // ignore fatal errors and log them as fatal instead
                        Logger<ThreadedMessageQueue<TData>>.Fatal(logError);
                    }
                }
            }
            #endregion
        }
    }
}
